/*
 * Copyright (c) 2020.Huawei Technologies Co., Ltd. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cstring>
#include "MxBase/Log/Log.h"
#include "MxStream/StreamManager/MxStreamManager.h"
#include "opencv4/opencv2/opencv.hpp"

#include "rapidjson/document.h"
#include "rapidjson/prettywriter.h"
#include "rapidjson/stringbuffer.h"
#include <iostream>

using namespace rapidjson;
using namespace cv;


namespace {
APP_ERROR ReadFile(const std::string& filePath, MxStream::MxstDataInput& dataBuffer)
{
    char c[PATH_MAX + 1] = { 0x00 };
    size_t count = filePath.copy(c, PATH_MAX + 1);
    if (count != filePath.length()) {
        LogError << "Failed to copy file path(" << c << ").";
        return APP_ERR_COMM_FAILURE;
    }
    // Get the absolute path of input file
    char path[PATH_MAX + 1] = { 0x00 };
    if ((strlen(c) > PATH_MAX) || (realpath(c, path) == nullptr)) {
        LogError << "Failed to get image, the image path is (" << filePath << ").";
        return APP_ERR_COMM_NO_EXIST;
    }
    // Open file with reading mode
    FILE *fp = fopen(path, "rb");
    if (fp == nullptr) {
        LogError << "Failed to open file (" << path << ").";
        return APP_ERR_COMM_OPEN_FAIL;
    }
    // Get the length of input file
    fseek(fp, 0, SEEK_END);
    long fileSize = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    // If file not empty, read it into FileInfo and return it
    if (fileSize > 0) {
        dataBuffer.dataSize = fileSize;
        dataBuffer.dataPtr = new (std::nothrow) uint32_t[fileSize];
        if (dataBuffer.dataPtr == nullptr) {
            LogError << "allocate memory with \"new uint32_t\" failed.";
            fclose(fp);
            return APP_ERR_COMM_FAILURE;
        }

        uint32_t readRet = fread(dataBuffer.dataPtr, 1, fileSize, fp);
        if (readRet == 0) {
            fclose(fp);
            return APP_ERR_COMM_READ_FAIL;
        }
        fclose(fp);
        return APP_ERR_OK;
    }
    fclose(fp);
    return APP_ERR_COMM_FAILURE;
}

std::string ReadPipelineConfig(const std::string& pipelineConfigPath)
{
    std::ifstream file(pipelineConfigPath.c_str(), std::ifstream::binary);
    if (!file) {
        LogError << pipelineConfigPath <<" file dose not exist.";
        return "";
    }
    file.seekg(0, std::ifstream::end);
    uint32_t fileSize = file.tellg();
    file.seekg(0);
    std::unique_ptr<char[]> data(new char[fileSize]);
    file.read(data.get(), fileSize);
    file.close();
    std::string pipelineConfig(data.get(), fileSize);
    return pipelineConfig;
}
}

int main(int argc, const char* argv[])
{
    // read image file and build stream input
//    std::string picFileName = "../data/test.jpg";
//    std::string picFileName_result = "../out/test_result.jpg";
//    std::string picFileName = "../data/test_tiger.jpg";
//    std::string picFileName_result = "../out/test_tiger_result.jpg";
    std::string picFileName = "../data/test_rabbit2.jpg";
    std::string picFileName_result = "../out/test_rabbit2_result.jpg";

    MxStream::MxstDataInput dataBuffer;
    APP_ERROR ret = ReadFile( picFileName, dataBuffer);
    if (ret != APP_ERR_OK) {
        LogError << "Failed to read image file, ret = " << ret << ".";
        return ret;
    }
    // read pipeline config file
    std::string pipelineConfigPath = "../pipeline/Sample.pipeline";
//    std::string pipelineConfigPath = "../pipeline/YoloV5.pipeline";
//    std::string pipelineConfigPath = "../pipeline/yoloV3.pipeline";
//    std::string pipelineConfigPath = "../pipeline/yoloV3-resnet50.pipeline";

    std::string pipelineConfig = ReadPipelineConfig(pipelineConfigPath);
    if (pipelineConfig == "") {
        LogError << "Read pipeline failed.";
        return APP_ERR_COMM_INIT_FAIL;
    }
    // init stream manager
    MxStream::MxStreamManager mxStreamManager;
    ret = mxStreamManager.InitManager();
    if (ret != APP_ERR_OK) {
        LogError << "Failed to init Stream manager, ret = " << ret << ".";
        return ret;
    }
    // create stream by pipeline config file
    ret = mxStreamManager.CreateMultipleStreams(pipelineConfig);
    if (ret != APP_ERR_OK) {
        LogError << "Failed to create Stream, ret = " << ret << ".";
        return ret;
    }
    std::string streamName = "classification+detection";
    int inPluginId = 0;
    // send data into stream
    ret = mxStreamManager.SendData(streamName, inPluginId, dataBuffer);
    if (ret != APP_ERR_OK) {
        LogError << "Failed to send data to stream, ret = " << ret << ".";
        return ret;
    }
    // get stream output
    MxStream::MxstDataOutput* output = mxStreamManager.GetResult(streamName, inPluginId);
    if (output == nullptr) {
        LogError << "Failed to get pipeline output.";
        return ret;
    }

    std::string result = std::string((char *)output->dataPtr, output->dataSize);
    LogWarn << "Results:" << result;

    std::string myClassName1 , myClassName2;

    rapidjson::Document dom;
    unsigned int i,j;

    if(!dom.Parse(result.c_str()).HasParseError()) {
        if(dom.HasMember("MxpiObject") && dom["MxpiObject"].IsArray()){
            const rapidjson::Value& arr= dom["MxpiObject"];

            for (i = 0; i < arr.Size(); ++i) {
                const rapidjson::Value& obj = arr[i];

                if(obj.HasMember("MxpiClass") && obj["MxpiClass"].IsArray()){
                    const rapidjson::Value& arr2= obj["MxpiClass"];
                    for (j = 0; j < arr2.Size(); ++j) {
                        const rapidjson::Value& obj2 = arr2[j];
                        if(obj2.HasMember("className") && obj2["className"].IsString()){
                            myClassName1 = obj2["className"].GetString();
                            std::cout << "className:" << myClassName1 << std::endl;
                        }
                        else{
                        }
                    }//end of for j
                }

                if(obj.HasMember("classVec") && obj["classVec"].IsArray()){
                    const rapidjson::Value& arr2= obj["classVec"];
                    for (j = 0; j < arr2.Size(); ++j) {
                        const rapidjson::Value& obj2 = arr2[j];
                        if(obj2.HasMember("className") && obj2["className"].IsString()){
                           myClassName2 = obj2["className"].GetString();
                            std::cout << "className:" << myClassName2 << std::endl;
                        }
                        else{
                        }
                    }//end of for j
                }
            } //end of for i
        }
        else{
        }
    }
    else{
        std::cout << "Parse Result Json string error" << std::endl;
    }


    web::json::value jsonText = web::json::value::parse(result);
    if (jsonText.is_object()) {
        web::json::object textObject = jsonText.as_object();

        auto itInferObject = textObject.find("MxpiObject");
        if (itInferObject == textObject.end() || (!itInferObject->second.is_array())) {
            return 0;
        }
        auto iter = itInferObject->second.as_array().begin();
        cv::Mat src = cv::imread( picFileName);
        for (; iter != itInferObject->second.as_array().end(); iter++) {
            if (iter->is_object()) {
                auto modelInferObject = iter->as_object();
                float x0 = 0;
                float x1 = 0;
                float y0 = 0;
                float y1 = 0;
                auto it = modelInferObject.find("x0");
                if (it != modelInferObject.end()) {
                    x0 = float(it->second.as_double());
                }
                it = modelInferObject.find("x1");
                if (it != modelInferObject.end()) {
                    x1 = float(it->second.as_double());
                }
                it = modelInferObject.find("y0");
                if (it != modelInferObject.end()) {
                    y0 = float(it->second.as_double());
                }
                it = modelInferObject.find("y1");
                if (it != modelInferObject.end()) {
                    y1 = float(it->second.as_double());
                }

                cv::Rect rect(x0, y0, x1 - x0, y1 - y0);
                cv::rectangle(src, rect, cv::Scalar(0, 255, 0),5, cv::LINE_8,0);
                cv::putText(src,myClassName1,cv::Point(x0+50,y0+50),FONT_HERSHEY_SIMPLEX,1,cv::Scalar(255,255,0));
                cv::putText(src,myClassName2,cv::Point(x0+50,y0+100),FONT_HERSHEY_SIMPLEX,1,cv::Scalar(255,255,0));
            }
        }



        cv::imwrite( picFileName_result, src);
        std::cout << picFileName_result << " produced in the directory:/root/AscendProjects/Resnet50/out." << std::endl;
    }

    // destroy streams
    mxStreamManager.DestroyAllStreams();
    delete dataBuffer.dataPtr;
    dataBuffer.dataPtr = nullptr;

    delete output;
    return 0;
}